package com.jxin.util.serialize.handler.impl;

import cn.hutool.json.JSONUtil;
import com.jxin.util.serialize.consts.SerializerHandlerEnum;
import com.jxin.util.serialize.err.SerializeExc;
import com.jxin.util.serialize.handler.ISerializeHandler;
import io.protostuff.LinkedBuffer;
import io.protostuff.ProtostuffIOUtil;
import io.protostuff.Schema;
import io.protostuff.runtime.RuntimeSchema;
import org.apache.commons.lang3.ArrayUtils;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;

import javax.validation.constraints.NotNull;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Collections;
import java.util.List;

/**
 * ProtoStuff 序列化器
 * @author Jxin
 * @version 1.0
 * @since 2020/1/21 11:13
 */
public class ProtoStuffSerializeHandler implements ISerializeHandler {
    /**LinkedBuffer 默认长度*/
    private static final Integer LINKED_BUFFER_DEF_SIZE= 1024 * 1024;
    /**
     * 获取 序列化执行器实现 的类型
     * @return 序列化执行器实现 的类型
     * @author Jxin
     */
    @Override
    public Integer type() {
        return SerializerHandlerEnum.PROTO_STUFF.getType();
    }

    /**
     * 序列化的对象
     * 1.入参为null时 返回空 字节数组
     * @param obj 要序列化的对象
     * @param <T> 对象类型
     * @throws SerializeExc 序列化失败时
     * @return 序列化后的字节数组
     * @author Jxin
     */
    @Override
    public <T> byte[] serialize(T obj) {
        if(obj == null){
            return new byte[0];
        }
        final LinkedBuffer buffer = LinkedBuffer.allocate(LINKED_BUFFER_DEF_SIZE);
        try {
            return ProtostuffIOUtil.toByteArray(obj,
                    (Schema<T>) RuntimeSchema.getSchema(obj.getClass()),
                    buffer);
        } catch (Exception e) {
            throw new SerializeExc(e,"[ProtoStuff序列化],单对象序列化失败, obj:{}", obj);
        } finally {
            buffer.clear();
        }
    }
    /**
     * 反序列化对象
     * 1.字节数组为空数组时  解析出来的对象为 null
     * @param objByteArr 对象的字节数组
     * @param clazz      对象的字节码类型
     * @param <T>        对象类型
     * @throws SerializeExc 反序列化失败时
     * @throws IllegalArgumentException 反序列化指定类 为null
     * @return 对象实例
     * @author Jxin
     */
    @Override
    public <T> T deserialize(byte[] objByteArr, @NotNull Class<T> clazz) {
        if(ArrayUtils.isEmpty(objByteArr)){
            return null;
        }
        Assert.notNull(clazz, "non null clazz required");
        final T result;
        try {
            result = clazz.newInstance();
            ProtostuffIOUtil.mergeFrom(objByteArr, result, RuntimeSchema.getSchema(clazz));
        } catch (InstantiationException | IllegalAccessException e) {
            throw new SerializeExc(e, "[ProtoStuff序列化],单对象反序列化失败, clazz:{}", clazz);
        }
        return result;
    }
    /**
     * 序列化对象列表
     * 1.入参为空时 返回空 字节数组
     * @param objList 要序列化的对象列表
     * @param <T>     对象类型
     * @throws SerializeExc 序列化失败时
     * @return 序列化后的字节数组
     * @author Jxin
     */
    @Override
    public <T> byte[] serializeList(List<T> objList) {
        if(CollectionUtils.isEmpty(objList)){
            return new byte[0];
        }
        final LinkedBuffer buffer = LinkedBuffer.allocate(LINKED_BUFFER_DEF_SIZE);
        try(final ByteArrayOutputStream bos = new ByteArrayOutputStream()) {
            ProtostuffIOUtil.writeListTo(bos,
                    objList,
                    (Schema<T>) RuntimeSchema.getSchema(objList.get(0).getClass()),
                    buffer);
            return bos.toByteArray();
        } catch (Exception e) {
            throw new SerializeExc(e, "[ProtoStuff序列化],多对象序列化失败, obj: {}", JSONUtil.toJsonStr(objList));
        }finally {
            buffer.clear();
        }
    }
    /**
     * 反序列化对象列表
     * 1.字节数组为空数组时  解析出来的对象为 空列表
     * @param  objByteArr 对象字节数组
     * @param  clazz      字节码类型
     * @param  <T>        对象类型
     * @throws SerializeExc 反序列化失败时
     * @throws IllegalArgumentException 反序列化指定类 为null
     * @return 对象列表
     * @author Jxin
     */
    @Override
    public <T> List<T> deserializeList(byte[] objByteArr, @NotNull Class<T> clazz) {
        if(ArrayUtils.isEmpty(objByteArr)){
            return Collections.emptyList();
        }
        Assert.notNull(clazz,"non null clazz required");
        Schema<T> schema = RuntimeSchema.getSchema(clazz);
        List<T> result;
        try {
            result = ProtostuffIOUtil.parseListFrom(new ByteArrayInputStream(objByteArr), schema);
        } catch (IOException e) {
            throw new SerializeExc(e,"[ProtoStuff序列化],多对象反序列化失败, clazz:{}", clazz);
        }
        return result;
    }
}
